﻿using System;
using System.Collections.Generic;
using System.Linq;
using System.Text;
using Microsoft.Crm.Sdk.Samples;
using Microsoft.Xrm.Sdk.Client;
using Microsoft.Crm.Services.Utility;
using System.ServiceModel.Description;
using CtefCrmImport.Common.Interfaces;
using Microsoft.Xrm.Sdk;
using Microsoft.Crm.Sdk.Messages;
using CtefCrmImport.Common.Extensions;

namespace CtefCrmImport.Common
{
    public class CrmRepository
    {
        private readonly ILogger logger;

        private Dictionary<string, EntityReferenceCollection> currencyMap;
        private IDictionary<string, EntityReferenceCollection> emailContactMap;
        private IDictionary<string, EntityReferenceCollection> fullNameContactMap;

        public CrmRepository(ILogger logger)
        {
            if (logger == null)
            {
                throw new ArgumentNullException("logger");
            }

            this.logger = logger;
        }

        public ServerConnection.Configuration GetConfig(string userName, string password, string serverAddress)
        {
            var dotIndex = serverAddress.IndexOf('.');
            var subdomain = serverAddress.Substring(0, dotIndex);
            var config = new ServerConnection.Configuration
            {
                ServerAddress = serverAddress,
                EndpointType = AuthenticationProviderType.LiveId,
                DiscoveryUri = new Uri(string.Format("https://{0}.crm.dynamics.com/XRMServices/2011/Discovery.svc", subdomain)),
                OrganizationName = "CTEF",
                OrganizationUri = new Uri(string.Format("https://{0}.api.crm.dynamics.com/XRMServices/2011/Organization.svc", subdomain)),
                DeviceCredentials = DeviceIdManager.LoadOrRegisterDevice()
            };

            // User credentials are set here
            config.Credentials = new ClientCredentials();
            config.Credentials.UserName.UserName = userName;
            config.Credentials.UserName.Password = password;

            return config;
        }

        public bool Validate(ServerConnection.Configuration config)
        {
            IEnumerable<CrmError> errors = null;
            var result = false;
            using (var serviceProxy = new OrganizationServiceProxy(config.OrganizationUri,
                                                                   config.HomeRealmUri,
                                                                   config.Credentials,
                                                                   config.DeviceCredentials))
            {
                // This statement is required to enable early-bound type support.
                serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());

                IOrganizationService service = serviceProxy;

                if (!CheckVersion(service))
                {
                    return false;
                }

                errors = BuildContactMaps(service);
                if (errors.IsNullOrEmpty())
                {
                    result = true;
                    logger.Log("Successfully validated existing data in CRM.");
                }
                else
                {
                    logger.Log(errors.Format());
                }
            }

            return result;
        }

        public bool ImportStudents(ServerConnection.Configuration config)
        {
            IEnumerable<CrmError> errors = null;
            var result = false;
            using (var serviceProxy = new OrganizationServiceProxy(config.OrganizationUri,
                                                                   config.HomeRealmUri,
                                                                   config.Credentials,
                                                                   config.DeviceCredentials))
            {
                // This statement is required to enable early-bound type support.
                serviceProxy.ServiceConfiguration.CurrentServiceEndpoint.Behaviors.Add(new ProxyTypesBehavior());

                IOrganizationService service = serviceProxy;

                if (!CheckVersion(service))
                {
                    return false;
                }

                errors = BuildContactMaps(service);
                if (errors.IsNullOrEmpty())
                {
                    result = true;
                    logger.Log("Successfully validated existing data in CRM.");
                }
                else
                {
                    logger.Log(errors.Format());
                }


            }

            return result;
        }

        public EntityReferenceCollection GetCurrency(IOrganizationService service, string currencyCode)
        {
            if (service == null)
            {
                throw new ArgumentNullException("service");
            }

            if (string.IsNullOrWhiteSpace(currencyCode))
            {
                throw new ArgumentException("Invalid currency code.", "currencyCode");
            }

            if (currencyMap.IsNullOrEmpty())
            {
                BuildCurrencyMap(service);
            }

            var key = currencyCode.ToKey(true);
            if (currencyMap != null && currencyMap.ContainsKey(key))
            {
                return currencyMap[key];
            }

            return null;
        }

        private IEnumerable<CrmError> BuildContactMaps(IOrganizationService service)
        {
            return BuildEmailContactMap(service).OrEmpty().Concat(BuildFullNameContactMap(service).OrEmpty());
        }

        private IEnumerable<CrmError> BuildEmailContactMap(IOrganizationService service)
        {
            var errors = new List<CrmError>();
            emailContactMap = new Dictionary<string, EntityReferenceCollection>();

            using (var orgSvcContext = new OrganizationServiceContext(service))
            {
                var contacts = orgSvcContext.CreateQuery("contact");
                foreach (var c in contacts)
                {
                    var email1 = c.GetAttributeValue<string>("emailaddress1");
                    var email2 = c.GetAttributeValue<string>("emailaddress2");
                    var email3 = c.GetAttributeValue<string>("emailaddress3");
                    var id = c.GetAttributeValue<Guid?>("contactid");

                    if (id.HasValue)
                    {
                        errors.AddRange(PopulateContactByEmail(email1, id.Value, emailContactMap).OrEmpty());
                        errors.AddRange(PopulateContactByEmail(email2, id.Value, emailContactMap).OrEmpty());
                        errors.AddRange(PopulateContactByEmail(email3, id.Value, emailContactMap).OrEmpty());
                    }
                }
            }

            return errors;
        }

        private IEnumerable<CrmError> BuildFullNameContactMap(IOrganizationService service)
        {
            var errors = new List<CrmError>();
            fullNameContactMap = new Dictionary<string, EntityReferenceCollection>();

            using (var orgSvcContext = new OrganizationServiceContext(service))
            {
                var contacts = orgSvcContext.CreateQuery("contact");
                foreach (var c in contacts)
                {
                    var name = c.GetAttributeValue<string>("fullname");

                    if (string.IsNullOrWhiteSpace(name) || string.Equals(name, Constants.DummyDonorName, StringComparison.OrdinalIgnoreCase)) continue;

                    name = name.ToKey(false);
                    var id = c.GetAttributeValue<Guid?>("contactid");

                    if (id.HasValue)
                    {
                        errors.AddRange(PopulateContactByName(name, id.Value, fullNameContactMap).OrEmpty());
                    }
                }
            }

            return errors;
        }

        private IEnumerable<CrmError> PopulateContactByName(string name, Guid id, IDictionary<string, EntityReferenceCollection> target)
        {
            if (string.IsNullOrWhiteSpace(name))
            {
                return new[] { new CrmError { Field = "name", Message = "Invalid name." } };
            }

            name = name.ToKey(false);
            if (!target.ContainsKey(name))
            {
                target[name] = new EntityReferenceCollection();
            }

            target[name].Add(new EntityReference("contact", id));

            if (1 < target[name].Count)
            {                
                return new[] { new CrmError { Message = "Identical name for multiple contacts detected: " + name } };
            }

            return null;
        }

        private IEnumerable<CrmError> PopulateContactByEmail(string email, Guid id, IDictionary<string, EntityReferenceCollection> target)
        {
            if (string.IsNullOrWhiteSpace(email))
            {
                return null;    // Not all contacts have emails. This is not an error condition.
            }

            email = email.ToKey(true);
            if (!target.ContainsKey(email))
            {
                target[email] = new EntityReferenceCollection();
            }

            if (target[email].Any(e => e.Id == id))
            {
                return null; // DO not add a new reference if the same contact id already exists
            }

            target[email].Add(new EntityReference("contact", id));

            if (1 < target[email].Count)
            {
                return new[] { new CrmError { Message = "Identical email for multiple contacts detected: " + email }};
            }

            return null;
        }

        private bool CheckVersion(IOrganizationService service)
        {
            var result = false;
            var versionRequest = new RetrieveVersionRequest();

            try
            {
                var versionResponse =
                    (RetrieveVersionResponse)service.Execute(versionRequest);
                logger.Log("Microsoft Dynamics CRM version {0}.", versionResponse.Version);
                result = true;
            }
            catch (Exception ex)
            {
                logger.Log(ex.Message);
            }

            return result;
        }

        private void BuildCurrencyMap(IOrganizationService service)
        {
            currencyMap = new Dictionary<string, EntityReferenceCollection>();
            using (var orgSvcContext = new OrganizationServiceContext(service))
            {
                var currencies = orgSvcContext.CreateQuery("transactioncurrency").ToArray();
                foreach (var c in currencies)
                {
                    var tc = c as TransactionCurrency;
                    if (tc == null) continue;

                    var key = tc.ISOCurrencyCode.ToKey(true);
                    if (currencyMap.ContainsKey(key)) continue;

                    currencyMap.Add(key, new EntityReferenceCollection { new EntityReference("transactioncurrency", tc.Id) });
                    logger.Log("Currency loaded: {0}", tc.ISOCurrencyCode);
                }
            }
        }
    }
}
